using System.Collections.Generic;
using System.Linq;
using NHibernate;
using TelerikMvcGridCustomBindingHelper.Mapper;
using TelerikMvcGridCustomBindingHelper.NHibernate.Util;
using TelerikMvcGridCustomBindingHelper.Projections;

namespace TelerikMvcGridCustomBindingHelper.NHibernate.Projections
{
    class ProjectionExpression<TSource> : IProjectionExpression<TSource> where TSource : class
    {
        private readonly IQueryOver<TSource, TSource> _source;
        private readonly ProjectionsOptionsImpl<TSource> _projectionsOptions;
        private readonly ProjectionsEngine _projectionsEngine = new ProjectionsEngine();

        public ProjectionExpression(IQueryOver<TSource, TSource> source, ProjectionsOptionsImpl<TSource> projectionsOptions)
        {
            _source = source;
            _projectionsOptions = projectionsOptions;
        }

        public IEnumerable<TResult> To<TResult>() where TResult : class
        {
            return GridModelMapper.Map<TSource, TResult>(From<TResult>());
        }

        public IEnumerable<TSource> From<TResult>() where TResult : class
        {
            GridModelMapper.AssertMappings<TSource, TResult>();

            var dynamicLinqSelector = _projectionsEngine.BuildSelector<TSource, TResult>(_projectionsOptions);

            SetProjections(dynamicLinqSelector.PropertyMaps.Select(pair => pair.Key).ToList());

            return _source.List();
        }

        private void SetProjections(List<string> propertyPaths)
        {
            var projections = global::NHibernate.Criterion.Projections.ProjectionList();

            foreach (var propertyPath in propertyPaths.Where(x => !x.Contains('.')))
            {
                projections.Add(global::NHibernate.Criterion.Projections.Property(string.Concat(_source.RootCriteria.Alias, ".", propertyPath)), propertyPath);
            }

            foreach (var alias in propertyPaths.Where(x => x.Contains('.')).Select(Generator.GetCriteriaAliasFor))
            {
                _source.CreateCriteria(alias);
                projections.Add(global::NHibernate.Criterion.Projections.Property(alias.SourceValue), alias.SourceValue);
            }

            _source.RootCriteria.SetProjection(projections);

            _source.TransformUsing(new ComplexResultTransformer(typeof(TSource)));
        }
    }
}